﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;

namespace OK.SCOM.Authoring.Tools.MPElementsExtract
{
	internal abstract class MPElementTypeBase
	{
		#region internal structs
		private struct FileInfoForSave
		{
			internal string FullName;
			internal string RelativeName;
		}
		#endregion

		internal string BaseDirectory { get; set; }
		internal string ExtractBaseDirectory { get; set; }
		internal virtual string Name {
			get
			{
				return "Base";
			}
		}

		internal virtual bool SupportsEmbed
		{
			get { return false; }
		}

		internal virtual bool SupportsExtractReplaceWithPlaceMarks
		{
			get { return false; }
		}

		private DirectoryInfo diBaseDirectory
		{
			get
			{
				if (Directory.Exists(BaseDirectory))
				{
					return new DirectoryInfo(BaseDirectory);
				}
				else throw new DirectoryNotFoundException(String.Format("'{0}' not found"));
			}
		}

		private DirectoryInfo diExtractBaseDirectory
		{
			get
			{
				if (!Directory.Exists(Path.Combine(BaseDirectory,ExtractBaseDirectory)))
				{
					diBaseDirectory.CreateSubdirectory(ExtractBaseDirectory);
				}
				return new DirectoryInfo(Path.Combine(BaseDirectory,ExtractBaseDirectory));
			}
		}

		internal MPElementTypeBase(string baseDirectory)
		{
			BaseDirectory = baseDirectory;
		}

		internal MPElementTypeBase(string baseDirectory, string extractBaseDirectory)
		{
			BaseDirectory = baseDirectory;
			ExtractBaseDirectory = extractBaseDirectory;
		}

		private FileInfoForSave TransformFilenameForSave(string name)
		{
			string fileRelativeName = name.Replace(":", "").Replace("..", "").Replace(" ", "").Replace("/", "\\");
			while (fileRelativeName.StartsWith("\\") || fileRelativeName.StartsWith("/"))
			{
				fileRelativeName = fileRelativeName.Remove(0, 1);
			}

			string[] fileNameParts = fileRelativeName.Split('\\');
			string path = diExtractBaseDirectory.FullName;
			string file = fileNameParts[fileNameParts.Length - 1];

			for (int i = 0; i < fileNameParts.Length - 1; ++i)
			{
				if (!Directory.Exists(Path.Combine(path, fileNameParts[i])))
				{
					DirectoryInfo di = new DirectoryInfo(path);
					di.CreateSubdirectory(fileNameParts[i]);
				}
				path = Path.Combine(path, fileNameParts[i]);
			}

			string fileFullName = Path.Combine(path, file);

			if (File.Exists(fileFullName))
			{
				File.Delete(fileFullName);
			}

			FileInfoForSave res = new FileInfoForSave();
			res.FullName = fileFullName;
			res.RelativeName = fileRelativeName;

			return res;
		}

		byte[] HexStringToBytes(string hex)
		{
			byte[] data = new byte[hex.Length / 2];
			int j = 0;
			for (int i = 0; i < hex.Length; i += 2)
			{
				data[j] = Convert.ToByte(hex.Substring(i, 2), 16);
				++j;
			}
			return data;
		}

		string BytesToHexString(byte[] bytes)
		{
			string data = BitConverter.ToString(bytes).Replace("-", string.Empty);
			return data;
		}

		protected string Save(string content, string name)
		{
			FileInfoForSave fileinfo = TransformFilenameForSave(name);

			TextWriter writer = new StreamWriter(fileinfo.FullName, false);
			writer.Write(content);
			writer.Close();

			return Path.Combine(ExtractBaseDirectory, fileinfo.RelativeName);
		}

		protected string SaveXml(string content, string name)
		{

			XmlDocument doc = new XmlDocument();
			doc.LoadXml(content);

			UTF8StringWriter writer = new UTF8StringWriter();
		
			doc.Save(writer);

			return Save(writer.ToString(), name);
		}

		protected string SaveBinary(string content, string name)
		{
			FileInfoForSave fileinfo = TransformFilenameForSave(name);

			byte[] data = HexStringToBytes(content);
			using (BinaryWriter writer = new BinaryWriter(File.Open(fileinfo.FullName, FileMode.Create)))
			{
				writer.Write(data);
			}

			return Path.Combine(ExtractBaseDirectory, fileinfo.RelativeName);
		}

		protected string Load(string path)
		{
			string res = "";
			
			string file = Path.Combine(diBaseDirectory.FullName, path);
			if (File.Exists(file))
			{
				StreamReader reader = File.OpenText(file);
				string content = reader.ReadToEnd();
				reader.Close();
				res = content;
			}
			else throw new FileNotFoundException(String.Format("File not found ({0}).",file));

			return res;
		}

		protected XmlDocument LoadXml(string path)
		{
			string content = Load(path);
			XmlDocument doc = new XmlDocument();
			doc.LoadXml(content);
			return doc;
		}

		protected string LoadBinary(string path)
		{
			string res = "";

			string file = Path.Combine(diBaseDirectory.FullName, path);
			if (File.Exists(file))
			{
				//FileStream reader = File.OpenRead(file);
				FileInfo fi = new FileInfo(file);
				BinaryReader reader = new BinaryReader(File.OpenRead(file));
				byte[] data = new byte[fi.Length];

				for(int i=0; i<fi.Length; ++i)
				{
					data[i]=reader.ReadByte();
				}
				
				reader.Close();
				res = BytesToHexString(data);
			}
			else throw new FileNotFoundException(String.Format("File not found ({0}).", file));

			return res;
		}

		protected string LoadXmlNoDeclaration(string path)
		{
			XmlDocument doc = LoadXml(path);
			if (doc.FirstChild.NodeType == XmlNodeType.XmlDeclaration)
			{
				doc.RemoveChild(doc.FirstChild);
			}

			UTF8StringWriter writer = new UTF8StringWriter();
			XmlWriterSettings settings = new XmlWriterSettings();
			settings.Indent = true;
			settings.OmitXmlDeclaration = true;

			XmlWriter xmlWriter = XmlWriter.Create(writer, settings);

			doc.Save(xmlWriter);
			string res = writer.ToString();
			return res;
		}

		internal void Parse(XmlNode node, ParseAction action)
		{
			ProcessSingleNodeBeforeChildren(node, action);
			foreach (XmlNode childNode in node.ChildNodes)
			{
				Parse(childNode, action);
				ProcessSingleNodeAfterChild(node, action);
			}
			ProcessSingleNodeAfterChildren(node, action);
		}

		protected void EmbedContent(XmlNode node)
		{
			string oldContent = node.InnerText;
			string newContent = "";

			string[] lines = oldContent.Split(new String[] { "\r\n" }, StringSplitOptions.None);
			foreach (string line in lines)
			{
				if (line.StartsWith("{{") && line.EndsWith("}}"))
				{
					string file = line.Replace("{{", "").Replace("}}", "");
					Console.WriteLine(String.Format("Embeding {0}", file));
					newContent += PrePocessLoadedContent(Load(file));
				}
				else newContent += line;
			}

			node.InnerText = newContent;
		}

		protected abstract void ProcessSingleNodeBeforeChildren(XmlNode node, ParseAction action);
		protected abstract void ProcessSingleNodeAfterChild(XmlNode node, ParseAction action);
		protected abstract void ProcessSingleNodeAfterChildren(XmlNode node, ParseAction action);

		protected virtual string PrePocessLoadedContent(string content)
		{
			return content;
		}
	}
}
